package com.kasax.plugin.app.transform

import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import com.android.utils.FileUtils
import com.kasax.plugin.app.LogUtils
import com.kasax.plugin.app.visitor.injectTime.ClassInjectTimeVisitor
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter

/**
 * 精简版Transfrom
 *
 * 参考资料:https://juejin.cn/post/7351206808117051426*/
class SimpleAsmTransfrom extends Transform {

    @Override
    String getName() {
        return "SimpleAsmTransfrom"
    }

    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    @Override
    boolean isIncremental() {
        // 每次全量处理
        return false
    }

    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        LogUtils.d(getName(), "------------ ------------  transform 开始  ------------ ------------  ")
        def outputProvider = transformInvocation.outputProvider
        // 清理旧产物
        outputProvider.deleteAll()
        def inputs = transformInvocation.inputs
        inputs.each { TransformInput input ->
            // jarInputs: 三方依赖与aar/jar产物
            // 1 处理依赖jar
            LogUtils.d(getName(), "handle jars")
            input.jarInputs.each { JarInput jarInput ->
                // jarInput.status: 新增/变更/删除
                LogUtils.d(getName(), "jarFile-path:${jarInput.file.absolutePath} , status: ${jarInput.status}")
                def srcJarFile = jarInput.file
                def destFile = outputProvider.getContentLocation(jarInput.name,
                        jarInput.contentTypes,
                        jarInput.scopes,
                        Format.JAR)
                LogUtils.d(getName(), "copy src-path:${srcJarFile.absolutePath} , to dest-path: ${destFile.absolutePath}")
                FileUtils.copyFile(srcJarFile, destFile)
            }
            // direcotryInputs:本地class
            LogUtils.d(getName(), "handle directorys")
            // 2. 处理源码目录
            input.directoryInputs.each { DirectoryInput directoryInput ->
                directoryInput.changedFiles.entrySet().each {
                    // 打印类名+变更类型
                    LogUtils.d(getName(), "direcotry-path:${directoryInput.file.absolutePath} , key: ${it.key.name} , " + "value: ${it.value.name()}")

                }
                def src = directoryInput.file
                def dest = outputProvider.getContentLocation(directoryInput.name,
                        directoryInput.contentTypes,
                        directoryInput.scopes,
                        Format.DIRECTORY)
                processInject(src, dest)
            }
        }
        LogUtils.d(getName(), "------------ ------------  transform 结束  ------------ ------------  ")
    }

    /**
     * ASM插桩的逻辑封装到此方法
     * @param src
     * @param dest
     */
    private void processInject(File src, File dest) {
        LogUtils.d(getName(), "processInject start")
        LogUtils.d(getName(), "processInject src:${src.absolutePath} , dest: ${dest.absolutePath}")
        // 遍历src下所有的class文件
        for (File file : FileUtils.getAllFiles(src)) {
            FileInputStream fis = new FileInputStream(file)
            ClassReader cr = new ClassReader(fis)
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES)
            cr.accept(new ClassInjectTimeVisitor(cw), ClassReader.EXPAND_FRAMES)
            def newClsBytes = cw.toByteArray()
            // 写入目标目录
            def fullClsPath = file.absolutePath.replace(src.absolutePath, "")
            def outFile = new File(dest, fullClsPath)
            LogUtils.d(getName(), "processInject outFile:${outFile.absolutePath}")
            FileUtils.mkdirs(outFile.parentFile)
            def fos = new FileOutputStream(outFile)
            fos.write(newClsBytes)
            fos.close()
        }
        LogUtils.d(getName(), "processInject complete")
    }
}